<!-- Discription: 大文件上传, author: ydj, Date: 2023-05-29 17:42:29 -->

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>大文件分片</title>
</head>

<body>
    <input type="file" id="uploader" @change="changeFile" />
    <progress id="progress" value="0"></progress>
    <p id="output"></p>
</body>
<script src="https://cdn.bootcdn.net/ajax/libs/spark-md5/3.0.2/spark-md5.min.js"></script>
<script>
    const inputDom = document.querySelector('input')

    inputDom.onchange = async (e) => {
        // 获取上传的文件
        const file = inputDom.files[0]
        // 文件分片
        function sliceFn(file, chunkSize = 1 * 1024 * 1024) {
            const result = []
            for (let i = 0; i < file.size; i += chunkSize) {
                result.push(file.slice(i, i + chunkSize))
            }
            return result
        }
        const chunks = sliceFn(file)

        console.log('文件分片', chunks)
        console.time()
        const hash = await calFileMd5Fn(chunks, (data) => {
            console.log('计算hash进度值', data)
        })
        console.log("hash值", hash)
        console.timeEnd()
    }
    /**
     * chunks 文件分片数组
     * progressCallbackFn 进度函数
     * */
    function calFileMd5Fn(chunks, progressCallbackFn) {
        return new Promise((resolve, reject) => {
            // 从第0块开始读取
            let currentChunk = 0
            // 实例化spark
            const spark = new SparkMD5.ArrayBuffer()
            // 实例化阅读器
            const fileReader = new FileReader()
            fileReader.onerror = reject
            fileReader.onload = (e) => {
                progressCallbackFn(Math.ceil(currentChunk / chunks.length * 100))
                // 将二进制文件追加到spark中
                spark.append(e.target.result)
                currentChunk += 1
                if (currentChunk < chunks.length) {
                    fileReader.readAsArrayBuffer(chunks[currentChunk])
                } else {
                    resolve(spark.end())
                }
            }
            // 文件读取器的readAsArrayBuffer方法开始读取文件，从blob数组中的第0项开始
            fileReader.readAsArrayBuffer(chunks[currentChunk])
        })
    }
</script>
<!-- <script>
    const inputDom = document.querySelector('input')

    inputDom.onchange = (e) => {
        console.time()
        // 获取上传的文件
        const file = inputDom.files[0]
        // 实例化spark-md5
        const spark = new SparkMD5.ArrayBuffer()
        // 实例化文件阅读器
        const fileReader = new FileReader()
        fileReader.addEventListener('loadend', (e) => {
            // 添加到spark算法中去
            spark.append(e.target.result)
            const hash = spark.end()
            console.log('文件的hash', hash)
            console.timeEnd()
        })
        fileReader.readAsArrayBuffer(file)
       
    }

</script> -->
<script>
    const express = require("express")
    const uploader = require("express-fileupload")
    const path = require("path")
    const {
        promises: {
            writeFile,
            appendFile,
        },
        existsSync
    } = require('fs')
    const app = express()
    const port = 3000

    app.use("/", express.static("public"))
    app.use(express.json())
    app.use(express.urlencoded({
        urlencoded: true
    }))
    app.use(uploader())

    app.get('/', (req, res) => {
        res.send("hello world")
    })
    app.post("/api/upload", (req, res) => {
        const { name, size, type, offset, hash } = req.body
        const { file } = req.files
        const ext = path.extname(name)
        const filename = path.resolve(__dirname, `./public/${hash}${ext}`);
        if (offset) {
            if (!existsSync(filename)) {
                res.status(400)
                    .send({
                        message: '文件不存在'
                    })
                return
            }
            await appendFile(filename, file.buffer)
            res.send({
                data: 'appended',
            })
            return;
        }
        await writeFile(filename, file.buffer);
        res.send({
            data: 'created'
        })
    })
    app.listen(port, () => {
        console.log("server is running at:", port
        )
    })
</script>
<script>
    const uploader = document.getElementById("uploader")
    const output = document.getElementById("output")
    const progress = document.getElementById("progress")
    async function read(file) {
        const reader = new FileReader();
        return new Promise((resolve, reject) => {
            reader.onload = function () {
                resolve(reader.result)
            }
            reader.onerror = reject;
            reader.readAsArrayBuffer(file)
        })

    }
    uploader.addEventListener('change', async (e) => {
        const { files } = e.target;
        const [file] = files;
        if (!file) {
            return
        }
        uploader.value = null
        const content = await read(file)
        const hash = Crypto.MD5(content)
        const { size, name, type } = file
        progress.max = size
        const chunkSize = 1 * 1024 * 1024
        let upload = 0;
        const local = localStorage.getItem(hash)
        if (local) {
            upload = Number(local)
        }
        const breakpoint = 1500 * 1024;
        while (upload < size) {
            const chunk = file.slice(upload, upload + chunkSize, type)
            const formData = new FormData()
            formData.append("name", name)
            formData.append("type", type)
            formData.append("size", size)
            formData.append("file", chunk)
            formData.append("offset", upload)

            try {
                await axios.post("/api/upload", formData)
            } catch (error) {
                output.innerHTML = "上传失败，" + e.message
                return
            }
            upload += chunk.size
            localStorage.setItem(hash, upload)
            progress.value = upload
            if (upload > breakpoint) {
                return
            }
        }
        output.innerHTML = "上传成功"
    })
</script>